Skip to content

feat: CLI-via-Goosed unified agent architecture with multi-agent routing#7238

Draft
bioinfornatics wants to merge 426 commits intoblock:mainfrom
bioinfornatics:feature/cli-via-goosed
Draft

feat: CLI-via-Goosed unified agent architecture with multi-agent routing#7238
bioinfornatics wants to merge 426 commits intoblock:mainfrom
bioinfornatics:feature/cli-via-goosed

Conversation

@bioinfornatics
Copy link

CLI-via-Goosed: Unified Agent Architecture

Summary

This PR introduces a unified architecture where the CLI communicates with agents through goosed (the server binary), aligning desktop and CLI on a single communication path. It also adds multi-agent orchestration with an intent router, ACP/A2A protocol compatibility, and comprehensive UI improvements.

Key Changes

🏗️ Architecture: CLI-via-Goosed

  • CLI now communicates through goosed server instead of directly instantiating agents
  • GoosedClient manages server lifecycle (spawn, health check, graceful shutdown)
  • Process discovery & reuse via PID state file (~/.config/goose/goosed.state)
  • goose service install|uninstall|status|logs for managed daemon lifecycle (systemd/launchd)

🤖 Multi-Agent System

  • GooseAgent: 7 behavioral modes (assistant, specialist, recipe_maker, app_maker, app_iterator, judge, planner)
  • CodingAgent: 8 SDLC modes (pm, architect, backend, frontend, qa, security, sre, devsecops)
  • IntentRouter: Keyword-based routing with fuzzy prefix matching and configurable confidence thresholds
  • OrchestratorAgent: LLM-based meta-coordinator with fallback to IntentRouter
  • Internal modes (judge, planner, recipe_maker) filtered from public discovery

📡 Protocol Compatibility

  • ACP (Agent Communication Protocol): Full run lifecycle (create → stream → complete/cancel), elicitation, await flows
  • A2A (Agent-to-Agent): Dynamic agent card generation from IntentRouter slots
  • RunStore: Single-mutex design with LRU eviction (MAX_COMPLETED_RUNS=1000), TOCTOU-safe resume
  • ACP-IDE WebSocket: Session mode state with available/current mode tracking, notification forwarding

📊 Analytics & Observability

  • Routing analytics endpoints: POST /analytics/routing/inspect, POST /analytics/routing/eval, GET /analytics/routing/catalog
  • Routing evaluation framework: YAML-based test sets (29 cases), per-agent/mode accuracy metrics, confusion matrix
  • OpenTelemetry spans: orchestrator.route, orchestrator.llm_classify, intent_router.route
  • AgentEvent::PlanCreated: New event variant for orchestration plan tracking

🖥️ UI Improvements

  • WorkBlockIndicator: Collapsible tool-call chains with auto-open streaming, live-update panel
  • Progressive message rendering: Two-tier final answer detection, suppress transient tool call flash
  • Agent management: Dedup agents by ID, mode switching
  • ReasoningDetailPanel: Enhanced for work blocks with streaming support
  • Refactored hooks: useChatStream split into streamReducer.ts + streamDecoder.ts (860→576 lines)

🔒 Security & Reliability

  • Concurrency limit (10) on all /runs endpoints via ServiceBuilder
  • Structured ErrorResponse on all 11 bare StatusCode returns in runs.rs
  • ErrorResponse::bad_request() and conflict() constructors added
  • AcpIdeSessions LRU eviction (MAX_IDE_SESSIONS=100) with idle timeout

Quality Gates

Gate Status
cargo build --all-targets
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test -p goose --lib (789 tests)
cargo test -p goose-server (40 tests)
npx tsc --noEmit
npx vitest run (325/326, 1 pre-existing)
npx eslint
Merge conflict check ✅ Clean

New Test Coverage

  • 14 RunStore lifecycle tests: create/get, status transitions, await/elicitation, cancellation, events, output, errors, pagination, eviction
  • 23 WorkBlock non-regression tests: streaming, completed, tool chains, final answer detection, dual indicator prevention
  • 7 routing evaluation tests: YAML parsing, accuracy thresholds, metrics computation, report generation
  • 6 SSE parser tests: event boundary handling, multi-event buffers, partial data
  • 6 IntentRouter tests: keyword routing, fallback, disabled agents

Routing Evaluation Baseline

Overall: 41.4% (keyword router — LLM router pending)
Goose Agent:  100% (5/5)
Coding Agent:  33% (8/24)
Best modes:  architect 100%, qa 100%
Worst modes: frontend 0%, devsecops 0%, backend 20%

Files Changed

  • Rust: ~30 new/modified files across goose, goose-server, goose-cli, goose-mcp
  • TypeScript/React: ~15 new/modified files in ui/desktop
  • Tests: 50+ new tests (Rust + Vitest)
  • Docs: Architecture review, analytics backlog, protocol analysis

Follow-up Work

  • BL-2: Analytics UI dashboard (3-tab React page)
  • BL-3: Live user feedback (👍/👎 on routing)
  • Agent extraction: QA, PM, Security as standalone agents
  • LLM-based router (replace keyword matching)
  • Full OTel dashboard integration

@michaelneale
Copy link
Collaborator

thanks @bioinfornatics I like that general idea - looks like a lot of work to tidy up conflicts but would like to see what it looks like if you could show it here.

@bioinfornatics bioinfornatics force-pushed the feature/cli-via-goosed branch 6 times, most recently from e8dc59e to 4b68302 Compare February 17, 2026 12:59
@DOsinga
Copy link
Collaborator

DOsinga commented Feb 17, 2026

this is a massive change! I like the ideas, but I think we should discuss some of them separately (where we do we want to go with clients?) but also, it seems to introduce 5 big ideas, shouldn't we split those up?

Replace the DropdownMenu for project actions with a hover-triggered
vertical icon strip that appears outside the panel on hover:

  Normal:                    On Hover:
  │ 📂 goose-project     │   │ 📂 goose-project  [+]│
  │                       │   │                   [📌]│
  │                       │   │                   [✕] │

Changes:
- Remove DropdownMenu dependency entirely
- Action strip uses absolute positioning (right-0 -top-1)
- Icons appear on group/project hover (opacity-0 → opacity-100)
- Close button uses destructive hover color
- Remove unused MoreVertical import
- Add relative positioning to parent for absolute children
…topPropagation

- Remove SidebarHeader with burger/Goose label
- Add SidebarEdgeStrip: 6px clickable edge on sidebar right side
  - Hover reveals bg-border-strong/40, click toggles sidebar
  - Cursor changes: w-resize (expanded) / e-resize (collapsed)
- Action icons (+, pin, x) now inline with hover highlight
  - Added e.stopPropagation() to prevent parent toggle
  - Smaller padding (p-0.5), hover feedback on icons
  - Close button title simplified
- Remove unused useSidebar/SidebarHeader/SidebarTrigger from AppSidebar
- Always-visible 1px border-r on sidebar edge (border-border-default)
- Hover: thickens to 3px border-border-strong (clear visual feedback)
- Active: 3px border-border-accent (confirms interaction)
- Width bumped to w-2 (8px) for easier click targeting
- Cursor: w-resize (expanded) / e-resize (collapsed)
Replace static edge strip with interactive drag handle:
- Pointer capture drag to resize sidebar width (38px-192px)
- Snap threshold at 120px: drag past = expand, drag under = collapse
- Protruding tab handle (›/‹) visible on sidebar edge
- Click-to-toggle when no drag detected
- Invisible 20px hit zone for easy drag targeting
- Smooth CSS transitions (disabled during drag)
- Keyboard accessible (Enter/Space to toggle)
Change sidebar-wrapper from overflow-hidden to overflow-y-hidden + overflow-x-visible
so the protruding tab handle (›/‹) is visible outside the sidebar bounds.
… on hover

- Tab handle is invisible at rest (no border, no bg, no text)
- On hover: shows border-default, bg-muted, text-muted chevron
- On active: bg-active, text-default
- Smaller: 14x32px (was 18x44px)
- Tab is inside the drag zone (not a separate element)
- Maintains drag-to-resize and click-to-toggle behavior
1. Right-side sidebar support: inverts drag direction for side='right'
2. Touch safety: touch-none prevents browser gesture interference
3. pointercancel handling: endDrag() prevents stuck isDragging state
4. Robust sidebar selector: [data-slot='sidebar'][data-state] query
5. Auto-calculated snap threshold: (ICON + FULL) / 2 = 115px
6. group-hover/sidebar-drag: tab visible on drag zone hover
7. ARIA: aria-expanded on drag handle for screen readers

Also removes unused SNAP_THRESHOLD constant.
instances.ts is a hand-written A2A wrapper that was living in src/api/,
the output directory of @hey-api/openapi-ts. Every codegen run deleted it.

- Move src/api/instances.ts → src/lib/instances.ts
- Update all 7 consumer imports to new path
- Remove Justfile git-checkout workaround (no longer needed)
- Internal imports updated to reach ../api/ from new location
- SidebarMenuButton base: add icon-mode classes (size-8, p-2, span:hidden)
  so icons remain visible when sidebar is collapsed
- SidebarDragHandle: replace protruding tab with full-height 6px strip,
  hover:bg-border-default for contrast, wider invisible hit area for grab
- AppSidebar: add icon-only buttons for multi-item zones in collapsed mode,
  wrap full collapsible groups in group-data-[collapsible=icon]:hidden
- Default: 1px bg-border-default (subtle separator line, always visible)
- Hover: widens to 4px with bg-border-strong for clear affordance
- Dragging: 4px bg-border-strong with grabbing cursor
- Invisible 16px hit area maintained for easy grabbing
…snap animation

- SNAP_THRESHOLD at 30% (~84px) instead of 50% (~115px) — much easier to fold
- rubberBand() gives physical resistance when dragging past min/max edges
- Disable CSS transition during drag for real-time feedback
- Re-enable transition on release for smooth snap to final state
- e.preventDefault() on pointerDown to avoid text selection
- Proper cancel handling cleans up both --sidebar-width and transition
Remove all pointer drag logic (rubber-band, snap threshold, deadzone,
pointer capture) and replace with a simple SidebarToggleStrip:
- 6px always-visible strip (border-default/40) at sidebar edge
- Widens to 8px on hover with stronger contrast
- Click toggles sidebar open/collapsed
- Chevron indicator appears on hover
- Keyboard accessible (Enter/Space)
- ARIA expanded state

108 lines removed, 16 added — much simpler, cleaner UX.
- Remove standalone Home SidebarGroup with Collapsible wrapper
- Replace with 'New Chat' button + FolderPlus dropdown + flat SessionList
- General project group in SessionList naturally acts as home
- Remove unused isChatExpanded state
- Simpler structure: one section for sessions, separator, then nav zones
… on /pair when no session

Task 1: Replace Radix Tooltip in EnvironmentBadge with native title attr — eliminates
Maximum update depth exceeded error caused by Portal mount/unmount cycles.

Task 3: PromptBar now shows on /pair when no session is registered (ctx.session === null).
Removed redundant isOnPairRoute check from PromptBar — showPromptBar in context handles it.
Cleaned up unused useLocation import.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants